package com.djl.configuration;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.proxy.DruidDriver;
import com.djl.annotation.Repository;
import com.djl.exception.BootException;
import com.djl.supports.PluginInjector;
import org.apache.commons.io.IOUtils;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ObjectUtils;
import org.yaml.snakeyaml.Yaml;

import javax.sql.DataSource;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Driver;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class ClassPathMapperScanner extends org.mybatis.spring.mapper.ClassPathMapperScanner{

    private static final String REPOSITORY_ATTRIBUTE_MULTI_DIALECT = "dialect";

    private static final String MAPPER_BEAN_NAME = "sqlSessionFactory";

    private static final String RESOURCE = "application.yml";

    private static final String DEFAULTHEAD = "datasource";

    //处理后期事务,数据源必须是单例的
    public static final DruidDataSource DATASOURCE = new DruidDataSource();

    private PluginInjector pluginInjector = new PluginInjector();


    public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    static{
        loadDatasourceParam(DATASOURCE);
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        beanDefinitionHolders.forEach(h -> {
            try{
                MutablePropertyValues mutablePropertyValues = h.getBeanDefinition().getPropertyValues();
                AnnotationMetadata annotationMetadata = ((ScannedGenericBeanDefinition) h.getBeanDefinition()).getMetadata();
                if(annotationMetadata.hasAnnotation(Repository.class.getName())){
                    //获取接口名称
                    String repositoryName = ((ScannedGenericBeanDefinition) h.getBeanDefinition()).getMetadata().getClassName();
                    Map<String, Object> props = annotationMetadata.getAnnotationAttributes(Repository.class.getName());
                    Configuration configuration = createMybatisConfig(Configuration.class);
                    if(props.containsKey(REPOSITORY_ATTRIBUTE_MULTI_DIALECT) && (boolean) props.get(REPOSITORY_ATTRIBUTE_MULTI_DIALECT)){
                        //方言是指处理系统 迁移/重构 导致数据库不统一
                        logger.info(repositoryName + "启用SQL方言");
                        configuration = createMybatisConfig(DialectConfiguration.class);
                    }
                    //开启驼峰命名
                    configuration.setMapUnderscoreToCamelCase(true);

                    pluginInjector.inject(configuration,Class.forName(repositoryName));

                    mutablePropertyValues.add(MAPPER_BEAN_NAME,createSqlSessionFactory(getDatasource(), configuration));
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        });
        return beanDefinitionHolders;
    }

    private DataSource getDatasource() {
        if(DATASOURCE==null){
            return new DruidDataSource();
        }
        return DATASOURCE;
    }

    private Configuration createMybatisConfig(Class<? extends Configuration> configurationClass) throws Exception {
        Configuration configuration = configurationClass.newInstance();
        //支持null设置JDBC类型
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        //关闭mybatis二级缓存，注意mybatis默认开启一级缓存
        configuration.setCacheEnabled(false);
        return configuration;
    }

    public SqlSessionFactory createSqlSessionFactory(DataSource dataSource, Configuration configuration) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        MybatisProperties properties = new MybatisProperties();
        properties.setMapperLocations(new String[]{"classpath*:/repository/**/*Repository.xml"});
        factory.setConfiguration(configuration);
        factory.setConfigurationProperties(properties.getConfigurationProperties());
        if (!ObjectUtils.isEmpty(properties.resolveMapperLocations())) {
            factory.setMapperLocations(properties.resolveMapperLocations());
        }
        return factory.getObject();
    }

    /**
     * 默认读取数据源,数据源必须配置在 application.yml
     */
    private static void loadDatasourceParam(DruidDataSource datasource) {
        try {
            InputStream bootstrapConfig = ClassPathMapperScanner.class.getClassLoader().getResourceAsStream(RESOURCE);
            if (bootstrapConfig != null && bootstrapConfig.available() != 0) {
                Map<String, Object> content = (new Yaml()).load(IOUtils.toString(bootstrapConfig));
                LinkedHashMap<String,Object> varParamMap = (LinkedHashMap<String,Object>)content.get(DEFAULTHEAD);
                //循环赋值
                propertyValuation(datasource,varParamMap);
            } else {
                throw new BootException("未找到配置："+RESOURCE);
            }
        } catch (Exception var2) {
            var2.printStackTrace();
        }
    }

    /**
     * 循环赋值
     */
    private static void propertyValuation(DruidDataSource datasource, LinkedHashMap<String, Object> varParamMap) {
       try {
           datasource.setUsername(varParamMap.get("username").toString());
           datasource.setPassword(varParamMap.get("password").toString());
           datasource.setDriverClassName(varParamMap.get("driver-class-name").toString());
           datasource.setUrl(varParamMap.get("url").toString());
       }catch (Exception e){
           e.printStackTrace();
       }
    }
}

